package geeorm

import (
	"database/sql"
	"fmt"
	"geeorm/dialect"
	"geeorm/log"
	"geeorm/session"
	_ "github.com/mattn/go-sqlite3"
	"strings"
)

type Engine struct {
	db *sql.DB
	dialect dialect.Dialect
}

func NewEngine(driver,source string)(e *Engine,err error){
	db,err:=sql.Open(driver,source)
	if err!=nil{
		log.Error(err)
		return
	}
	if err=db.Ping();err!=nil{
		log.Error(err)
		return
	}
	dial,ok:=dialect.GetDialect(driver)
	if !ok{
		log.Errorf("dialect %s NOT FOUND",driver)
		return
	}
	e=&Engine{db: db,dialect: dial}
	log.Info("Connect database success")
	return
}

func (engine *Engine)Close(){
	if err:=engine.db.Close();err!=nil{
		log.Error("Failed to close database")
	}
	log.Info("Close database success")
}

func (engine *Engine)NewSession()*session.Session{
	return session.New(engine.db,engine.dialect)
}

type TxFunc func(*session.Session)(interface{},error)

func (engine *Engine)Transaction(f TxFunc)(result interface{},err error){
	s:=engine.NewSession()
	if err:=s.Begin();err!=nil{
		return nil,err
	}
	defer func() {
		if p:=recover();p!=nil{
			s.Rollback()
			panic(p)
		}else if err!=nil{
			s.Rollback()
		}else{
			err=s.Commit()
		}
	}()
	return f(s)
}

//difference用来计算前后两个字段切片地差集。新表-旧表=新增字段，旧表-新表=删除字段
//使用Alter语句新增字段
//使用创建信标并重命名的方式删除字段
func difference(a []string,b []string)(diff []string){
	mapB:=make(map[string]bool)
	for _,v:=range b{
		mapB[v]=true
	}
	for _,v:=range a{
		if _,ok:=mapB[v];!ok{
			diff=append(diff,v)
		}
	}
	return
}

func (engine *Engine)Migrate(value interface{})error{
	_,err:=engine.Transaction(func(s *session.Session) (result interface{}, err error) {
		if !s.Model(value).HasTable(){
			log.Infof("table %s doesn't exist",s.RefTable().Name)
			return nil,s.CreateTable()
		}
		table:=s.RefTable()
		rows,_:=s.Raw(fmt.Sprintf("SELECT * FROM %s LIMIT 1",table.Name)).QueryRows()
		columns,_:=rows.Columns()
		addCols:=difference(table.FieldNames,columns)
		delCols:=difference(columns,table.FieldNames)
		log.Infof("added cols %v,deleted cols %v",addCols,delCols)
		for _,col:=range addCols{
			f:=table.GetField(col)
			sqlStr:=fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s;",table.Name,f.Name,f.Type)
			if _,err=s.Raw(sqlStr).Exec();err!=nil{
				return
			}
		}
		if len(delCols)==0{
			return
		}
		tmp:="tmp_"+table.Name
		fieldStr:=strings.Join(table.FieldNames,", ")
		s.Raw(fmt.Sprintf("CREATE TABLE %s AS SELECT %s FROM %s;",tmp,fieldStr,table.Name))
		s.Raw(fmt.Sprintf("DROP TABLE %s;",table.Name))
		s.Raw(fmt.Sprintf("ALTER TABLE %s RENAME TO %s;",tmp,table.Name))
		_,err=s.Exec()
		return
	})
	return err
}